use crate::{Clipboard, ClipboardWatcher};
use crate::{
    ClipboardContent, ClipboardHandler, ContentFormat, RustImageData,
    common::{Result, RustImage},
};
use std::sync::mpsc::{self, Receiver, Sender};
use std::{
    sync::{Arc, RwLock},
    thread,
    time::{Duration, Instant},
};
use x11rb::{
    COPY_DEPTH_FROM_PARENT, CURRENT_TIME,
    connection::Connection,
    protocol::{
        Event, xfixes,
        xproto::{
            Atom, AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, Property,
            SELECTION_NOTIFY_EVENT, SelectionNotifyEvent, SelectionRequestEvent, WindowClass,
        },
    },
    rust_connection::RustConnection,
    wrapper::ConnectionExt as _,
};

x11rb::atom_manager! {
    pub Atoms: AtomCookies {
        CLIPBOARD,
        CLIPBOARD_MANAGER,
        PROPERTY,
        SAVE_TARGETS,
        TARGETS,
        ATOM,
        INCR,
        TIMESTAMP,
        MULTIPLE,

        UTF8_STRING,
        UTF8_MIME_0: b"text/plain;charset=utf-8",
        UTF8_MIME_1: b"text/plain;charset=UTF-8",
        // Text in ISO Latin-1 encoding
        // See: https://tronche.com/gui/x/icccm/sec-2.html#s-2.6.2
        STRING,
        // Text in unknown encoding
        // See: https://tronche.com/gui/x/icccm/sec-2.html#s-2.6.2
        TEXT,
        TEXT_MIME_UNKNOWN: b"text/plain",
        // Rich Text Format
        RTF: b"text/rtf",
        RTF_1: b"text/richtext",
        HTML: b"text/html",
        PNG_MIME: b"image/png",
        FILE_LIST: b"text/uri-list",
        GNOME_COPY_FILES: b"x-special/gnome-copied-files",
        NAUTILUS_FILE_LIST: b"x-special/nautilus-clipboard",
    }
}

pub const DEFAULT_READ_TIMEOUT: u64 = 500;

// zh: 用于创建 X11 剪贴板上下文的选项
// en: Options for creating an X11 clipboard context
pub struct ClipboardContextX11Options {
    // zh: 剪贴板读取操作超时
    // en: Timeout for clipboard read operations
    pub read_timeout: Option<Duration>,
}

const FILE_PATH_PREFIX: &str = "file://";
pub struct ClipboardContext {
    inner: Arc<InnerContext>,
    read_timeout: Option<Duration>,
}

struct ClipboardData {
    format: Atom,
    data: Vec<u8>,
}

struct InnerContext {
    server: XServerContext,
    server_for_write: XServerContext,
    ignore_formats: Vec<Atom>,
    // 此刻待写入的剪贴板内容
    wait_write_data: RwLock<Vec<ClipboardData>>,
}

impl InnerContext {
    pub fn new() -> Result<Self> {
        let server = XServerContext::new()?;
        let server_for_write = XServerContext::new()?;
        let wait_write_data = RwLock::new(Vec::new());

        let ignore_formats = vec![
            server.atoms.TIMESTAMP,
            server.atoms.MULTIPLE,
            server.atoms.TARGETS,
            server.atoms.SAVE_TARGETS,
        ];

        Ok(Self {
            server,
            server_for_write,
            ignore_formats,
            wait_write_data,
        })
    }

    pub fn handle_selection_request(&self, event: SelectionRequestEvent) -> Result<()> {
        let success;
        let ctx = &self.server_for_write;
        let atoms = ctx.atoms;
        // we are asked for a list of supported conversion targets
        if event.target == atoms.TARGETS {
            let reader = self.wait_write_data.read();
            match reader {
                Ok(data_list) => {
                    let mut targets = Vec::with_capacity(10);
                    targets.push(atoms.TARGETS);
                    targets.push(atoms.SAVE_TARGETS);
                    if data_list.len() > 0 {
                        data_list.iter().for_each(|data| {
                            targets.push(data.format);
                        });
                    }
                    ctx.conn.change_property32(
                        PropMode::REPLACE,
                        event.requestor,
                        event.property,
                        AtomEnum::ATOM,
                        &targets,
                    )?;
                    success = true;
                }
                Err(_) => return Err("Failed to read clipboard data".into()),
            }
        } else {
            let reader = self.wait_write_data.read();
            match reader {
                Ok(data_list) => {
                    success = match data_list.iter().find(|d| d.format == event.target) {
                        Some(data) => {
                            ctx.conn.change_property8(
                                PropMode::REPLACE,
                                event.requestor,
                                event.property,
                                event.target,
                                &data.data,
                            )?;
                            true
                        }
                        None => false,
                    };
                }
                Err(_) => return Err("Failed to read clipboard data".into()),
            }
        }
        // on failure, we notify the requester of it
        let property = if success {
            event.property
        } else {
            AtomEnum::NONE.into()
        };
        // tell the requester that we finished sending data
        ctx.conn.send_event(
            false,
            event.requestor,
            EventMask::NO_EVENT,
            SelectionNotifyEvent {
                response_type: SELECTION_NOTIFY_EVENT,
                sequence: event.sequence,
                time: event.time,
                requestor: event.requestor,
                selection: event.selection,
                target: event.target,
                property,
            },
        )?;
        ctx.conn.flush()?;
        Ok(())
    }

    pub fn process_event(
        &self,
        buff: &mut Vec<u8>,
        selection: Atom,
        target: Atom,
        property: Atom,
        timeout: Option<Duration>,
        sequence_number: u64,
    ) -> Result<()> {
        let mut is_incr = false;
        let start_time = if timeout.is_some() {
            Some(Instant::now())
        } else {
            None
        };
        let ctx = &self.server;
        let atoms = ctx.atoms;
        loop {
            if timeout
                .into_iter()
                .zip(start_time)
                .next()
                .map(|(timeout, time)| (Instant::now() - time) >= timeout)
                .unwrap_or(false)
            {
                return Err("Timeout while waiting for clipboard data".into());
            }

            let (event, seq) = match ctx.conn.poll_for_event_with_sequence()? {
                Some(event) => event,
                None => {
                    thread::park_timeout(Duration::from_millis(50));
                    continue;
                }
            };

            if seq < sequence_number {
                continue;
            }

            match event {
                Event::SelectionNotify(event) => {
                    if event.selection != selection {
                        continue;
                    };

                    let target_type = {
                        if target == atoms.TARGETS {
                            atoms.ATOM
                        } else {
                            target
                        }
                    };

                    let reply = ctx
                        .conn
                        .get_property(
                            false,
                            event.requestor,
                            event.property,
                            target_type,
                            buff.len() as u32,
                            u32::MAX,
                        )?
                        .reply()?;

                    if reply.type_ == atoms.INCR {
                        if let Some(mut value) = reply.value32() {
                            if let Some(size) = value.next() {
                                buff.reserve(size as usize);
                            }
                        }
                        ctx.conn.delete_property(ctx.win_id, property)?.check()?;
                        is_incr = true;
                        continue;
                    } else if reply.type_ != target && reply.type_ != atoms.ATOM {
                        return Err("Clipboard data type mismatch".into());
                    }
                    buff.extend_from_slice(&reply.value);
                    break;
                }

                Event::PropertyNotify(event) if is_incr => {
                    if event.state != Property::NEW_VALUE {
                        continue;
                    };

                    let cookie =
                        ctx.conn
                            .get_property(false, ctx.win_id, property, AtomEnum::ATOM, 0, 0)?;

                    let length = cookie.reply()?.bytes_after;

                    let cookie = ctx.conn.get_property(
                        true,
                        ctx.win_id,
                        property,
                        AtomEnum::NONE,
                        0,
                        length,
                    )?;
                    let reply = cookie.reply()?;
                    if reply.type_ != target {
                        continue;
                    };

                    let value = reply.value;

                    if !value.is_empty() {
                        buff.extend_from_slice(&value);
                    } else {
                        break;
                    }
                }
                _ => (),
            }
        }
        Ok(())
    }
}

impl ClipboardContext {
    pub fn new() -> Result<Self> {
        Self::new_with_options(ClipboardContextX11Options {
            read_timeout: Some(Duration::from_millis(DEFAULT_READ_TIMEOUT)),
        })
    }

    pub fn new_with_options(options: ClipboardContextX11Options) -> Result<Self> {
        // build connection to X server
        let ctx = InnerContext::new()?;
        let ctx_arc = Arc::new(ctx);
        let ctx_clone = ctx_arc.clone();

        thread::spawn(move || {
            let res = process_server_req(&ctx_clone);
            if let Err(e) = res {
                println!("process_server_req error: {:?}", e);
            }
        });

        Ok(Self {
            inner: ctx_arc,
            read_timeout: options.read_timeout,
        })
    }

    fn read(&self, format: &Atom) -> Result<Vec<u8>> {
        let ctx = &self.inner.server;
        let atoms = ctx.atoms;
        let clipboard = atoms.CLIPBOARD;
        let win_id = ctx.win_id;
        let cookie =
            ctx.conn
                .convert_selection(win_id, clipboard, *format, atoms.PROPERTY, CURRENT_TIME)?;
        let sequence_num = cookie.sequence_number();
        cookie.check()?;
        let mut buff = Vec::new();

        self.inner.process_event(
            &mut buff,
            clipboard,
            *format,
            atoms.PROPERTY,
            self.read_timeout,
            sequence_num,
        )?;

        ctx.conn.delete_property(win_id, atoms.PROPERTY)?.check()?;

        Ok(buff)
    }

    fn write(&self, data: Vec<ClipboardData>) -> Result<()> {
        let writer = self.inner.wait_write_data.write();
        match writer {
            Ok(mut writer) => {
                writer.clear();
                writer.extend(data);
            }
            Err(_) => return Err("Failed to write clipboard data".into()),
        }
        let ctx = &self.inner.server_for_write;
        let atoms = ctx.atoms;

        let win_id = ctx.win_id;
        let clipboard = atoms.CLIPBOARD;
        ctx.conn
            .set_selection_owner(win_id, clipboard, CURRENT_TIME)?
            .check()?;

        if ctx
            .conn
            .get_selection_owner(clipboard)?
            .reply()
            .map(|reply| reply.owner == win_id)
            .unwrap_or(false)
        {
            Ok(())
        } else {
            Err("Failed to take ownership of the clipboard".into())
        }
    }
}

fn process_server_req(context: &InnerContext) -> Result<()> {
    let atoms = context.server_for_write.atoms;
    loop {
        match context
            .server_for_write
            .conn
            .wait_for_event()
            .map_err(|e| format!("wait_for_event error: {:?}", e))?
        {
            Event::DestroyNotify(_) => {
                // This window is being destroyed.
                println!("Clipboard server window is being destroyed x_x");
                break;
            }
            Event::SelectionClear(event) => {
                // Someone else has new content in the clipboard, so it is
                // notifying us that we should delete our data now.
                println!("Somebody else owns the clipboard now");
                if event.selection == atoms.CLIPBOARD {
                    // Clear the clipboard contents
                    context
                        .wait_write_data
                        .write()
                        .map(|mut writer| writer.clear())
                        .map_err(|e| format!("write clipboard data error: {:?}", e))?;
                }
            }
            Event::SelectionRequest(event) => {
                // Someone is requesting the clipboard content from us.
                context
                    .handle_selection_request(event)
                    .map_err(|e| format!("handle_selection_request error: {:?}", e))?;
            }
            Event::SelectionNotify(event) => {
                // We've requested the clipboard content and this is the answer.
                // Considering that this thread is not responsible for reading
                // clipboard contents, this must come from the clipboard manager
                // signaling that the data was handed over successfully.
                if event.selection != atoms.CLIPBOARD_MANAGER {
                    println!(
                        "Received a `SelectionNotify` from a selection other than the CLIPBOARD_MANAGER. This is unexpected in this thread."
                    );
                    continue;
                }
            }
            _event => {
                // May be useful for debugging but nothing else really.
                // trace!("Received unwanted event: {:?}", event);
            }
        }
    }
    Ok(())
}

impl Clipboard for ClipboardContext {
    //https://source.chromium.org/chromium/chromium/src/+/main:ui/base/x/x11_clipboard_helper.cc;l=224;drc=4cc063ac39c4a0d1f6011421b259a9715bb16de1;bpv=0;bpt=1
    fn available_formats(&self) -> Result<Vec<String>> {
        let ctx = &self.inner.server;
        let atoms = ctx.atoms;
        self.read(&atoms.TARGETS).map(|data| {
            let mut formats = Vec::new();
            // 解析原子标识符列表
            let atom_list: Vec<Atom> = parse_atom_list(&data);
            for atom in atom_list {
                if self.inner.ignore_formats.contains(&atom) {
                    continue;
                }
                let atom_name = ctx.get_atom_name(atom).unwrap_or("Unknown".to_string());
                formats.push(atom_name);
            }
            formats
        })
    }

    fn has(&self, format: crate::ContentFormat) -> bool {
        let ctx = &self.inner.server;
        let atoms = ctx.atoms;
        let atom_list = self.read(&atoms.TARGETS).map(|data| parse_atom_list(&data));
        match atom_list {
            Ok(formats) => match format {
                ContentFormat::Text => formats.contains(&atoms.UTF8_STRING),
                ContentFormat::Rtf => formats.contains(&atoms.RTF),
                ContentFormat::Html => formats.contains(&atoms.HTML),
                ContentFormat::Image => formats.contains(&atoms.PNG_MIME),
                ContentFormat::Files => formats.contains(&atoms.FILE_LIST),
                ContentFormat::Other(format_name) => {
                    let atom = ctx.get_atom(format_name.as_str());
                    match atom {
                        Ok(atom) => formats.contains(&atom),
                        Err(_) => false,
                    }
                }
            },
            Err(_) => false,
        }
    }

    fn clear(&self) -> Result<()> {
        self.write(vec![])
    }

    fn get_buffer(&self, format: &str) -> Result<Vec<u8>> {
        let atom = self.inner.server.get_atom(format);
        match atom {
            Ok(atom) => self.read(&atom),
            Err(_) => Err("Invalid format".into()),
        }
    }

    fn get_text(&self) -> Result<String> {
        let atoms = self.inner.server.atoms;
        let text_data = self.read(&atoms.UTF8_STRING);
        text_data.map_or_else(
            |_| Ok("".to_string()),
            |data| Ok(String::from_utf8_lossy(&data).to_string()),
        )
    }

    fn get_rich_text(&self) -> Result<String> {
        let atoms = self.inner.server.atoms;
        let rtf_data = self.read(&atoms.RTF);
        rtf_data.map_or_else(
            |_| Ok("".to_string()),
            |data| Ok(String::from_utf8_lossy(&data).to_string()),
        )
    }

    fn get_html(&self) -> Result<String> {
        let atoms = self.inner.server.atoms;
        let html_data = self.read(&atoms.HTML);
        html_data.map_or_else(
            |_| Ok("".to_string()),
            |data| Ok(String::from_utf8_lossy(&data).to_string()),
        )
    }

    fn get_image(&self) -> Result<crate::RustImageData> {
        let atoms = self.inner.server.atoms;
        let image_bytes = self.read(&atoms.PNG_MIME);
        match image_bytes {
            Ok(bytes) => {
                let image = RustImageData::from_bytes(&bytes);
                match image {
                    Ok(image) => Ok(image),
                    Err(_) => Err("Invalid image data".into()),
                }
            }
            Err(_) => Err("No image data found".into()),
        }
    }

    fn get_files(&self) -> Result<Vec<String>> {
        let atoms = self.inner.server.atoms;
        let file_list_data = self.read(&atoms.FILE_LIST);
        file_list_data.map_or_else(
            |_| Ok(vec![]),
            |data| {
                let file_list_str = String::from_utf8_lossy(&data).to_string();
                let mut list = Vec::new();
                for line in file_list_str.lines() {
                    if !line.starts_with(FILE_PATH_PREFIX) {
                        continue;
                    }
                    list.push(line.to_string())
                }
                Ok(list)
            },
        )
    }

    fn get(&self, formats: &[ContentFormat]) -> Result<Vec<ClipboardContent>> {
        let mut contents = Vec::new();
        for format in formats {
            match format {
                ContentFormat::Text => match self.get_text() {
                    Ok(text) => contents.push(ClipboardContent::Text(text)),
                    Err(_) => continue,
                },
                ContentFormat::Rtf => match self.get_rich_text() {
                    Ok(rtf) => contents.push(ClipboardContent::Rtf(rtf)),
                    Err(_) => continue,
                },
                ContentFormat::Html => match self.get_html() {
                    Ok(html) => contents.push(ClipboardContent::Html(html)),
                    Err(_) => continue,
                },
                ContentFormat::Image => match self.get_image() {
                    Ok(image) => contents.push(ClipboardContent::Image(image)),
                    Err(_) => continue,
                },
                ContentFormat::Files => match self.get_files() {
                    Ok(files) => contents.push(ClipboardContent::Files(files)),
                    Err(_) => continue,
                },
                ContentFormat::Other(format_name) => match self.get_buffer(format_name) {
                    Ok(buffer) => {
                        contents.push(ClipboardContent::Other(format_name.clone(), buffer))
                    }
                    Err(_) => continue,
                },
            }
        }
        Ok(contents)
    }

    fn set_buffer(&self, format: &str, buffer: Vec<u8>) -> Result<()> {
        let atom = self.inner.server_for_write.get_atom(format)?;
        let data = ClipboardData {
            format: atom,
            data: buffer,
        };
        self.write(vec![data])
    }

    fn set_text(&self, text: String) -> Result<()> {
        let atoms = self.inner.server_for_write.atoms;
        let text_bytes = text.as_bytes().to_vec();

        let data = ClipboardData {
            format: atoms.UTF8_STRING,
            data: text_bytes,
        };
        self.write(vec![data])
    }

    fn set_rich_text(&self, text: String) -> Result<()> {
        let atoms = self.inner.server_for_write.atoms;
        let text_bytes = text.as_bytes().to_vec();

        let data = ClipboardData {
            format: atoms.RTF,
            data: text_bytes,
        };
        self.write(vec![data])
    }

    fn set_html(&self, html: String) -> Result<()> {
        let atoms = self.inner.server_for_write.atoms;
        let html_bytes = html.as_bytes().to_vec();

        let data = ClipboardData {
            format: atoms.HTML,
            data: html_bytes,
        };
        self.write(vec![data])
    }

    fn set_image(&self, image: RustImageData) -> Result<()> {
        let atoms = self.inner.server_for_write.atoms;
        let image_png = image.to_png()?;
        let data = ClipboardData {
            format: atoms.PNG_MIME,
            data: image_png.get_bytes().to_vec(),
        };
        self.write(vec![data])
    }

    fn set_files(&self, files: Vec<String>) -> Result<()> {
        let atoms = self.inner.server_for_write.atoms;
        let data = file_uri_list_to_clipboard_data(files, atoms);
        self.write(data)
    }

    fn set(&self, contents: Vec<ClipboardContent>) -> Result<()> {
        let mut data = Vec::new();
        let atoms = self.inner.server_for_write.atoms;
        for content in contents {
            match content {
                ClipboardContent::Text(text) => {
                    data.push(ClipboardData {
                        format: atoms.UTF8_STRING,
                        data: text.as_bytes().to_vec(),
                    });
                }
                ClipboardContent::Rtf(rtf) => {
                    data.push(ClipboardData {
                        format: atoms.RTF,
                        data: rtf.as_bytes().to_vec(),
                    });
                }
                ClipboardContent::Html(html) => {
                    data.push(ClipboardData {
                        format: atoms.HTML,
                        data: html.as_bytes().to_vec(),
                    });
                }
                ClipboardContent::Image(image) => {
                    let image_png = image.to_png()?;
                    data.push(ClipboardData {
                        format: atoms.PNG_MIME,
                        data: image_png.get_bytes().to_vec(),
                    });
                }
                ClipboardContent::Files(files) => {
                    let data_arr = file_uri_list_to_clipboard_data(files, atoms);
                    data.extend(data_arr);
                }
                ClipboardContent::Other(format_name, buffer) => {
                    let atom = self.inner.server_for_write.get_atom(&format_name)?;
                    data.push(ClipboardData {
                        format: atom,
                        data: buffer,
                    });
                }
            }
        }
        self.write(data)
    }
}

pub struct ClipboardWatcherContext<T: ClipboardHandler> {
    handlers: Vec<T>,
    stop_signal: Sender<()>,
    stop_receiver: Receiver<()>,
}

unsafe impl<T: ClipboardHandler> Send for ClipboardWatcherContext<T> {}

impl<T: ClipboardHandler> ClipboardWatcherContext<T> {
    pub fn new() -> Result<Self> {
        let (tx, rx) = mpsc::channel();
        Ok(Self {
            handlers: Vec::new(),
            stop_signal: tx,
            stop_receiver: rx,
        })
    }
}

impl<T: ClipboardHandler> ClipboardWatcher<T> for ClipboardWatcherContext<T> {
    fn add_handler(&mut self, f: T) -> &mut Self {
        self.handlers.push(f);
        self
    }

    fn start_watch(&mut self) {
        let watch_server = XServerContext::new().expect("Failed to create X server context");
        let screen = watch_server
            .conn
            .setup()
            .roots
            .get(watch_server._screen)
            .expect("Failed to get screen");

        xfixes::query_version(&watch_server.conn, 5, 0)
            .expect("Failed to query version xfixes is not available");
        let cookie = xfixes::select_selection_input(
            &watch_server.conn,
            screen.root,
            watch_server.atoms.CLIPBOARD,
            xfixes::SelectionEventMask::SET_SELECTION_OWNER
                | xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE
                | xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY,
        )
        .expect("Failed to select selection input");

        cookie.check().unwrap();

        loop {
            if self
                .stop_receiver
                .recv_timeout(Duration::from_millis(500))
                .is_ok()
            {
                break;
            }
            let event = match watch_server
                .conn
                .poll_for_event()
                .expect("Failed to poll for event")
            {
                Some(event) => event,
                None => {
                    continue;
                }
            };
            if let Event::XfixesSelectionNotify(_) = event {
                self.handlers
                    .iter_mut()
                    .for_each(|handler| handler.on_clipboard_change());
            }
        }
    }

    fn get_shutdown_channel(&self) -> WatcherShutdown {
        WatcherShutdown {
            sender: self.stop_signal.clone(),
        }
    }
}

pub struct WatcherShutdown {
    sender: Sender<()>,
}

impl Drop for WatcherShutdown {
    fn drop(&mut self) {
        let _ = self.sender.send(());
    }
}

struct XServerContext {
    conn: RustConnection,
    win_id: u32,
    _screen: usize,
    atoms: Atoms,
}

impl XServerContext {
    fn new() -> Result<Self> {
        let (conn, screen) = x11rb::connect(None)?;
        let win_id = conn.generate_id()?;
        {
            let screen = conn.setup().roots.get(screen).unwrap();
            conn.create_window(
                COPY_DEPTH_FROM_PARENT,
                win_id,
                screen.root,
                0,
                0,
                1,
                1,
                0,
                WindowClass::INPUT_OUTPUT,
                screen.root_visual,
                &CreateWindowAux::new()
                    .event_mask(EventMask::STRUCTURE_NOTIFY | EventMask::PROPERTY_CHANGE),
            )?
            .check()?;
        }
        let atoms = Atoms::new(&conn)?.reply()?;
        Ok(Self {
            conn,
            win_id,
            _screen: screen,
            atoms,
        })
    }

    fn get_atom(&self, format: &str) -> Result<Atom> {
        let cookie = self.conn.intern_atom(false, format.as_bytes())?;
        Ok(cookie.reply()?.atom)
    }

    fn get_atom_name(&self, atom: Atom) -> Result<String> {
        let cookie = self.conn.get_atom_name(atom)?;
        Ok(String::from_utf8_lossy(&cookie.reply()?.name).to_string())
    }
}

// 解析原子标识符列表
fn parse_atom_list(data: &[u8]) -> Vec<Atom> {
    data.chunks(4)
        .map(|chunk| {
            let mut bytes = [0u8; 4];
            bytes.copy_from_slice(chunk);
            u32::from_ne_bytes(bytes)
        })
        .collect()
}

fn file_uri_list_to_clipboard_data(file_list: Vec<String>, atoms: Atoms) -> Vec<ClipboardData> {
    let uri_list: Vec<String> = file_list
        .iter()
        .map(|f| {
            if f.starts_with(FILE_PATH_PREFIX) {
                f.to_owned()
            } else {
                format!("{}{}", FILE_PATH_PREFIX, f)
            }
        })
        .collect();
    // 再构造一个 /home/xxx/xxx 这样的路径
    let uri_str_list: Vec<String> = file_list
        .iter()
        .map(|f| {
            if let Some(stripped) = f.strip_prefix(FILE_PATH_PREFIX) {
                stripped.to_owned()
            } else {
                f.to_owned()
            }
        })
        .collect();

    let data_text_plain = uri_str_list.join("\r\n");
    let data_text_utf8 = uri_str_list.join("\n");
    let data_text_uri_list = uri_list.join("\r\n");
    let data_gnome_copied_files = ["copy\n", uri_list.join("\n").as_str()].concat();

    vec![
        ClipboardData {
            format: atoms.TEXT_MIME_UNKNOWN,
            data: data_text_plain.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.UTF8_MIME_0,
            data: data_text_plain.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.STRING,
            data: data_text_utf8.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.TEXT,
            data: data_text_utf8.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.UTF8_STRING,
            data: data_text_utf8.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.FILE_LIST,
            data: data_text_uri_list.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.GNOME_COPY_FILES,
            data: data_gnome_copied_files.as_bytes().to_vec(),
        },
        ClipboardData {
            format: atoms.NAUTILUS_FILE_LIST,
            data: data_gnome_copied_files.as_bytes().to_vec(),
        },
    ]
}
